package com.saasovation.common.port.adapter.spring;

import com.saasovation.common.port.adapter.messaging.rabbitmq.ConnectionSettings;
import com.saasovation.common.port.adapter.persistence.eventsourcing.mysql.MySQLEventStore;
import com.zaxxer.hikari.HikariDataSource;
import org.hibernate.SessionFactory;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
import java.util.stream.Stream;

@Configuration
public class ApplicationConfiguration {

    @Bean
    public DataSource dataSource(Environment environment) {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .url(environment.getRequiredProperty("jdbc.url"))
                .username(environment.getRequiredProperty("jdbc.username"))
                .password(environment.getRequiredProperty("jdbc.password"))
                .driverClassName(environment.getRequiredProperty("jdbc.driverClassName"))
                .build();
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory(DataSource dataSource,
                                                  Environment environment) throws IOException {
        LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        Resource[] resources1 = patternResolver.getResources("classpath*:/*.hbm.xml");
        Resource[] resources2 = patternResolver.getResources("classpath*:/*.orm.xml");
        factoryBean.setMappingLocations(Stream.concat(
                        Stream.of(resources1),
                        Stream.of(resources2))
                .toArray(Resource[]::new));
        //
        Properties hibernateProperties = initHibernateProperties(environment);
        factoryBean.setHibernateProperties(hibernateProperties);
        return factoryBean;
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager(SessionFactory sessionFactory) {
        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(sessionFactory);
        return txManager;
    }

    @Bean
    public ConnectionSettings connectionSettings(Environment environment) {
        return ConnectionSettings
                .instance(environment.getProperty("spring.rabbitmq.host"),
                        Integer.parseInt(environment.getProperty("spring.rabbitmq.port", "5672")),
                        environment.getProperty("spring.rabbitmq.virtual-host", "/"),
                        environment.getProperty("spring.rabbitmq.username"),
                        environment.getProperty("spring.rabbitmq.password"));
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public MySQLEventStore eventStore(NamedParameterJdbcTemplate jdbcTemplate) {
        return new MySQLEventStore(jdbcTemplate);
    }

    private static Properties initHibernateProperties(Environment environment) {
        Properties hibernateProperties = new Properties();
        final String[] propertyNames = new String[]{"hibernate.show_sql",
                "hibernate.format_sql",
                "hibernate.hbm2ddl.auto",
                "hibernate.transform_hbm_xml.enabled",
        };
        for (String propertyName : propertyNames) {
            hibernateProperties.put(propertyName, environment.getRequiredProperty(propertyName));
        }
        return hibernateProperties;
    }
}
